import datetime
import requests
from bson import ObjectId
from flask import current_app as app

from app.db.models.jobs import Job
from app.db.models.assets import Asset
from app.db.models.asset_tasks import AssetTasks, AssetTaskSettings
from app.libs.utility import filter_cha
from app.libs.utils import enable_task_group
from app.libs import scanner
from app.libs.display import NODE_AREA_NAME_MAP
from app.libs.enums import AVAILABLE_TASK_GROUP_TUPLE
from app.config.settings import UA, ASSET_TASK_INTERVAL
from app.celery.sync_user_balance import sync_user_balances


class JobHelper(object):
    def __init__(self, request_args, uid=None, job_query=None, use_paginate=False, use_asset=False):
        self._query = {}
        verification = bool(int(request_args.get('isVerified', 0)))
        note = filter_cha(request_args.get('note', ''))
        target = filter_cha(request_args.get('target', ''))

        job_id = request_args.get('job_id')
        if job_id and not isinstance(job_id, ObjectId):
            job_id = ObjectId(job_id)
        self.page = int(request_args.get('page', 1))
        self.limit = int(request_args.get('limit', 10))
        self.use_paginate = use_paginate
        self.uid = uid
        self.use_asset = use_asset
        self._data = None
        self._count = 0

        if uid:
            self._query['uid'] = uid
        if job_id:
            self._query['_id'] = job_id
        if verification:
            self._query['verification__isVerified'] = verification
        if note:
            self._query['note'] = {'$regex': note}
        if target:
            self._query['target'] = {'$regex': target}
        if job_query:
            self._query.update(job_query)

        self._data = self.get_data()

    def get_data(self):
        if self._data:
            return self._data

        if self.use_paginate:
            data = Job.objects.find(self._query).order_by('-id').paginate(self.page, self.limit)
            if self.use_asset:
                self._add_asset(data.items)

            self._count = data.total
            return data.items

        data = Job.objects.find(self._query).order_by('-id')
        if self.use_asset:
            self._add_asset(data)

        self._count = len(data)
        return data

    def get_count(self):
        return self._count

    def _add_asset(self, job_data):
        job_ids = tuple(item.id for item in job_data)
        query = {'jobId': {'$in': job_ids}}
        if self.uid:
            query['uid'] = self.uid
        assets_dict = {asset.id: asset for asset in Asset.objects.find(query)}
        for _job in job_data:
            if _asset := assets_dict.get(_job.assetId):
                _job.assetResultId = _asset.resultId
                _job.assetSettings = _asset.taskSettings


class JobVerifyHelper(object):

    def __init__(self, job_id=None, job=None, uid=None):
        self.job = job
        if not self.job:
            query = {"id": job_id}
            if uid:
                query.update({"uid": uid})
            self.job = Job.objects.filter(**query).first()

    def remove_all_schedule_task(self):
        """关闭资产所有周期任务"""
        task_ids = set()
        try:
            asset_tasks = AssetTasks.objects.filter(jobId=self.job.id, triggerType="schedule")
            if not asset_tasks.count():
                return
            asset_tasks.update(enableMonitor=False)
            for at in asset_tasks:
                task_ids.update({i for i in at.get_task_ids_v2().values() if i})
        except Exception as e:
            app.logger.exception(e)
        scanner.BatchRemoveTask(task_ids).batch_remove()

    def create_asset_task(self):
        """创建变更监测任务asset_task记录"""
        asset = Asset.objects.filter(jobId=self.job.id).first()
        if not asset:
            return
        settings = AssetTaskSettings.from_dict(asset.taskSettings.to_dict())
        settings.interval = ASSET_TASK_INTERVAL
        settings.enable = True
        task_data = {
            "name": self.job.note,
            "taskGroupType": "web_asset",
            "triggerType": "schedule",
            "taskType": "asset",
            "targetUrl": self.job.targetUrl,
            "jobId": self.job.id,
            "uid": self.job.uid,
            "isVerified": True,
            "enableMonitor": settings.enable,
            "assetSettings": settings
        }
        asset_task = AssetTasks(**task_data).save()
        return asset_task

    def enable_asset_task(self):
        """在完成资产认证的时候下发变更监测任务"""
        self.job.reload()
        if (not self.job.verification.isVerified) or (self.job.assetType == "host"):
            return
        asset_task = AssetTasks.objects.filter(jobId=self.job.id, taskGroupType="web_asset", triggerType="schedule").first()
        if not asset_task:
            asset_task = self.create_asset_task()
        elif asset_task and not asset_task.enableMonitor:
            asset_task.update(enableMonitor=True)
        if asset_task:
            asset_task.reload()
            enable_task_group(asset_task, insert=True)
        else:
            app.logger.warnings(f'网站认证, job_id: {self.job.id}, 下发资产变更任务失败')

    def admin_asset_verify(self, is_verified, note=""):
        """鲁班后台管理员修改资产认证状态"""
        if not self.job:
            return
        if is_verified == self.job.verification.isVerified:
            return
        update_q = {
            "verification__isVerified": is_verified,
            "verification__note": note,
            "verification__verifyType": "ops",
            "verification__verifiedTime": datetime.datetime.utcnow()
        }
        self.job.update(**update_q)
        sync_user_balances(job_id=self.job.id)
        # 如果是从认证变更为未认证, 则停止资产所有周期任务
        if not is_verified:
            self.remove_all_schedule_task()
        # 从未认证修改为认证， 需要下发资产变更监测任务
        elif is_verified:
            self.enable_asset_task()
        return True

    def user_asset_verify(self, username):
        """前台用户认证资产"""
        if not self.job:
            app.logger.warnings(f'网站认证: {username}, 认证失败, 没有查询到对应的资产！')
            return False
        if self.job.verification.isVerified:
            return True
        challenge = self.job.verification.challenge
        challenge_url = f'{self.job.targetUrl}/{challenge}.txt'
        is_verified = self._asset_verify(self.job, challenge_url, challenge)
        if is_verified is not True:
            self.job.verification.isVerified = None
            self.job.enableMonitor = False
            self.job.enableAlert = False
            self.job.save()
            return False
        if is_verified:
            self.job.verification.isVerified = True
            self.job.verification.verifyType = 'file'
            self.job.verification.verifiedTime = datetime.datetime.utcnow()
        self.job.save()
        if is_verified and self.job.assetType != 'host':
            self.enable_asset_task()
        return True

    @staticmethod
    def _asset_verify(job, challenge_url, challenge):
        result = False
        detail = '网站认证: {}, 认证成功！'.format(job.note)
        try:
            headers = {'user-agent': UA}
            resp = requests.get(challenge_url, headers=headers, verify=False)
            if resp.status_code == 200 and resp.text.strip() == challenge:
                result = True
            else:
                detail = '网站认证: {}, 认证失败, 请求URL: {}， 请求状态码: {}，证书密钥: {}, 数据库密钥: {}！'.format(
                    job.note, challenge_url, resp.status_code, repr(resp.text), repr(challenge))
        except Exception as e:
            detail = '网站认证: {}, 认证失败, {}！'.format(job.note, str(e))
        app.logger.info(detail)
        return result


def get_available_task_log_detail(asset_task_ids):
    """组织新建、修改可用性任务配置详情日志"""
    fields = ("httpSettings", "pingSettings", "taskGroupType", "enableMonitor")
    asset_tasks = AssetTasks.objects.filter(
        id__in=asset_task_ids, taskGroupType__in=list(AVAILABLE_TASK_GROUP_TUPLE)).only(*fields)
    asset_task = asset_tasks.first()
    if not asset_task:
        return ""
    task_type = asset_task.taskGroupType
    if not (settings := getattr(asset_task, f"{task_type}Settings", {})):
        return ""
    targets = []
    for at in asset_tasks:
        try:
            targets.append(getattr(at, f"{task_type}Settings").to_mongo().to_dict().get("target"))
        except:
            continue
    if not targets:
        return ""
    status = "开启" if asset_task.enableMonitor else "关闭"
    settings = settings.to_mongo().to_dict()
    ipv4_area = settings.get("collect", {}).get("area", [])
    ipv4_area_name = [NODE_AREA_NAME_MAP.get(a, a) for a in ipv4_area]
    ipv6_area = settings.get("collect", {}).get("area_ipv6", [])
    ipv6_area_name = [NODE_AREA_NAME_MAP.get(a, a) for a in ipv6_area]
    interval = settings.get("interval")
    msg = f"任务类型: {task_type}, 任务状态: {status}, 监测频率: {int(interval/1000/60)}分钟/次, 监测地址: {','.join(targets)}, "
    if ipv4_area_name:
        msg += f"IPv4节点: {','.join(ipv4_area_name)}, "
    if ipv6_area_name:
        msg += f"IPv6节点: {','.join(ipv6_area_name)}, "
    if task_type == "ping":
        resp_time = settings.get("alert", {}).get("respTime")
        loss_rate = settings.get("alert", {}).get("lossRate")
        node_count = settings.get("alert", {}).get("nodeCount")
        alert_count = settings.get("alert", {}).get("alertCount")
        msg += (f"异常条件: 响应时间大于等于{resp_time}ms或丢包率大于等于{loss_rate}%时告警, "
                f"告警节点数: 节点异常大于等于{node_count}个时告警, "
                f"告警次数: 每次异常或故障最多告警{alert_count}次")
    else:
        method = settings.get("collect", {}).get("method", "get")
        payload = settings.get("collect", {}).get("payload", "")
        redirect = settings.get("collect", {}).get("redirect", 3)
        resp_time = settings.get("alert", {}).get("respTime")
        status_code_range = settings.get("alert", {}).get("statusCodeRange", [])
        status_code_range_msg = []
        for code in status_code_range:
            if isinstance(code, int):
                status_code_range_msg.append(code)
            elif isinstance(code, list) and len(code) == 2:
                status_code_range_msg.append(f"{min(code)}~{max(code)}")
            elif isinstance(code, list):
                status_code_range_msg.extend(code)
        node_count = settings.get("alert", {}).get("nodeCount")
        alert_count = settings.get("alert", {}).get("alertCount")
        msg += (f"请求方法: {method},"
                f"请求头信息: {payload},"
                f"重定向次数: {redirect},"
                f"异常条件: 响应时间大于等于{resp_time}ms或状态码在{'、'.join(status_code_range_msg)}范围时告警, "
                f"告警节点数: 节点异常大于等于{node_count}个时告警, "
                f"告警次数: 每次异常或故障最多告警{alert_count}次")
    return msg
